﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Threading;

namespace LibIpMsgNet
{
    public class RecvBuf {
	    public IPEndPoint  addr;
	    public int addrSize;
	    public int size;
	    public byte[]  msgBuf  = new byte[Consts.MAX_UDPBUF];
    }

    public class MsgBuf
    {
        public HostSub hostSub;
        public int version;
        public int portNo;
        public ulong packetNo;
        public ulong command;
        public int exOffset;			// expand message offset in msgBuf
        public int dummy;
        public byte[] msgBuf;

        public MsgBuf(MsgBuf org)
        {
            if (org == null)
            {
                hostSub = new HostSub();
                msgBuf = new byte[Consts.MAX_UDPBUF];
            }
            else
            {
                this.hostSub = org.hostSub;
                this.version = org.version;
                this.portNo = org.portNo;
                this.packetNo = org.packetNo;
                this.command = org.command;
                this.msgBuf = org.msgBuf;
            }
        }
    }

    public class MessageReceivedArgs : EventArgs
    {
        public RecvBuf buf;
    }

    public delegate void MessageReceivedHandle(object sender, MessageReceivedArgs args);

    public class MsgMng
    {
        private Socket udp_sd = null;
        private Socket tcp_sd = null;

        private ulong packetNo;

        private bool status;
        private HostSub local;

        public bool Status
        {
            get
            {
                return status;
            }
        }

        public HostSub LocalHost
        {
            get
            {
                return local;
            }
        }

        public MsgMng(ulong nicAddr, int portNo)
        {
            status = false;

            local = new HostSub();
            local.addr = nicAddr;
            local.portNo = portNo;
            local.hostName = Environment.MachineName;
            local.userName = Environment.UserName;

            packetNo = (ulong)DateTime.Now.Ticks;

            if (!InitSocket())
                return;

            status = true;

        }

        private bool InitSocket()
        {
            try
            {
                udp_sd = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
                tcp_sd = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP);

                IPAddress ip = new IPAddress((long)local.addr);
                IPEndPoint tempEnd = new IPEndPoint(ip, local.portNo);

                udp_sd.Bind(tempEnd);
                tcp_sd.Bind(tempEnd);

                udp_sd.Blocking = false;
                udp_sd.EnableBroadcast = true;
                udp_sd.SendBufferSize = Consts.MAX_SOCKBUF;
                udp_sd.ReceiveBufferSize = Consts.MAX_SOCKBUF;

                tcp_sd.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);

                tcp_sd.Listen(5);


                return true;
            }
            catch (Exception ex)
            {
                return false ;
            }
        }

        private void CloseSocket()
        {
            if (udp_sd != null)
            {
                udp_sd.Shutdown(SocketShutdown.Both);
                udp_sd.Close();
                udp_sd = null;
            }

            if (tcp_sd != null)
            {
                tcp_sd.Shutdown(SocketShutdown.Both);
                tcp_sd.Close();
                tcp_sd = null;
            }
        }

        private bool ResetSocket()
        {
            CloseSocket();
            return InitSocket();
        }

        public ulong MakePacketNo()
        {
            return packetNo++;
        }

        // OUT PACKET = VERSION:PACKETNO:USERNAME:HOSTNAME:COMMAND:MSG+EXMSG; RET = PACKETNO; OUT: LEN
        public ulong MakeMsg(out string buf, ulong _packetNo, ulong command, string  msg, string exMsg, out int packet_len)
        {
            buf = String.Format("{0}:{1}:{2}:{3}:{4}:{5}", IPMSG_DEF.IPMSG_VERSION, _packetNo, local.userName, local.hostName, command, msg);

            if (exMsg != null && !exMsg.Equals(""))
                buf = String.Format("{0}\0{1}\0", buf, exMsg);

            // here replace '\r\n' to '\n' : new line to unix
            buf.Replace("\r\n", "\n");
            
            //if (packet_len != null)
                packet_len = buf.Length;

            return _packetNo;
        }

        public bool UdpSend(ulong host_addr, int port_no, string buf, int len)
        {
            try
            {
                IPEndPoint ipe = new IPEndPoint(new IPAddress((long)host_addr), port_no);
                byte[] arr = Encoding.ASCII.GetBytes(buf);
                udp_sd.SendTo(arr, len, SocketFlags.None, ipe);

                return true;
            }
            catch (Exception e)
            {
                return false;
            }
        }

        public bool Send(ulong host, int port_no, ulong command, string message, string exMsg)
        {
            string buf;
            int len;

            MakeMsg(out buf, MakePacketNo(), command, message, exMsg, out len);
            return UdpSend(host, port_no, buf, len);
        }

        public bool UdpRecv(ref RecvBuf buf)
        {
            try
            {
                //EndPoint ep = (EndPoint)buf.addr;
                IPEndPoint ipe = new IPEndPoint(IPAddress.Any, (int)IPMSG_DEF.IPMSG_DEFAULT_PORT);
                EndPoint ep = (EndPoint)ipe;
                //buf.addrSize = udp_sd.ReceiveFrom((byte[])buf.msgBuf, buf.size-1, SocketFlags.None, ref ep);
                System.Collections.ArrayList socketList = new System.Collections.ArrayList(1);
                socketList.Add(udp_sd);
                Socket.Select(socketList, null, null, 1000);

                if (socketList.Count > 0)
                {
                    for (int i = 0; i < socketList.Count; i++)
                    {
                        Socket sk = (Socket)socketList[i];
                        buf.addrSize = sk.ReceiveFrom(buf.msgBuf, Consts.MAX_NAMEBUF - 1, SocketFlags.None, ref ep);
                        buf.addr.Address = ipe.Address;
                        buf.addr.Port = ipe.Port;
                        buf.msgBuf[buf.size] = 0;
                    }
                    return true;
                }
                return false;
            }
            catch (Exception ex)
            {
                return false;
            }
        }

        public bool Recv(ref MsgBuf msg)
        {
            RecvBuf buf = new RecvBuf();

            if (!UdpRecv(ref buf) || buf.size == 0)
                return false;

            return ResolveMsg(buf, ref msg);
        }


        // PACKET = VERSION:PACKETNO:USERNAME:HOSTNAME:COMMAND:MSG+EXMSG
        public bool ResolveMsg(RecvBuf buf, ref MsgBuf msg)
        {
            try
            {
                int pos, lastpos;
 
                msg.hostSub.addr = (ulong)buf.addr.Address.Address;
                msg.hostSub.portNo = buf.addr.Port;

                string str = Utils.ByteArrayToString(buf.msgBuf);

                // version
                pos = str.IndexOf(':', 0);
                if (pos < 0)
                    return false;
                else
                    msg.version = Convert.ToInt32(str.Substring(0, pos));

                // check version
                if ((ulong)msg.version != IPMSG_DEF.IPMSG_VERSION)
                    return false;

                // packetNo
                lastpos = pos;
                pos = str.IndexOf(':', lastpos + 1);
                if (pos < 0)
                    return false;
                else
                    msg.packetNo = (ulong)Convert.ToInt64(str.Substring(lastpos + 1, pos - lastpos - 1));

                // username
                lastpos = pos;
                pos = str.IndexOf(':', lastpos + 1);
                if (pos < 0)
                    return false;
                else
                    msg.hostSub.userName = str.Substring(lastpos + 1, pos - lastpos - 1);

                // hostname
                lastpos = pos;
                pos = str.IndexOf(':', lastpos + 1);
                if (pos < 0)
                    return false;
                else
                    msg.hostSub.hostName = str.Substring(lastpos + 1, pos - lastpos - 1);

                // command
                lastpos = pos;
                pos = str.IndexOf(':', lastpos + 1);
                if (pos < 0)
                    return false;
                else
                    msg.command = (ulong)Convert.ToInt64(str.Substring(lastpos + 1, pos - lastpos - 1));

                // Msg
                lastpos = pos;
                pos = str.IndexOf('\0', lastpos + 1);
                if (pos < 0)
                    return false;

                msg.exOffset = pos;

                // exMsg
                int msgpos = lastpos;
                lastpos = pos;
                pos = str.IndexOf('\0', lastpos + 1);

                // copy Msg and exMsg
                if (pos >= 0)
                    str.Substring(msgpos + 1, pos - msgpos - 1).ToCharArray().CopyTo(msg.msgBuf, 0);
                else
                    str.Substring(msgpos + 1, lastpos - msgpos - 1).ToCharArray().CopyTo(msg.msgBuf, 0); 

                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        
        private void ThreadUdpRecv()
        { 
        }


    }
}
